﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System.Collections;

namespace theguineasyndrome
{
    public class Level
    {
        private List<LevelElement> dynamicElements;
        private LevelCell[,] cells;

        private List<BackgroundElement> drawnBackgrounds;
        private List<ForegroundElement> drawnForegrounds;
        private List<ILevelObject> drawnObjects;

        private Dictionary<string, editor.PointMark> pointMarks;
        private Dictionary<string, Rectangle> zoneMarks;

        private ContentManager content;
        private Vector2 extremities;

        public Level(ContentManager content)
        {
            dynamicElements = new List<LevelElement>();
            drawnBackgrounds = new List<BackgroundElement>();
            drawnForegrounds = new List<ForegroundElement>();
            drawnObjects = new List<ILevelObject>();

            pointMarks = new Dictionary<string, editor.PointMark>();
            zoneMarks = new Dictionary<string, Rectangle>();

            this.content = content;
            extremities = new Vector2(0, 0);
        }

        public void moveElement(LevelElement element, Vector2 oldCenter)
        {
            Vector2 cellIndices = getCellIndices(oldCenter);
            Vector2 destinationIndices = getCellIndices(element.Center);
            if (cellIndices != destinationIndices)
            {
                cells[(int)cellIndices.X, (int)cellIndices.Y].removeElement(element);
                cells[(int)destinationIndices.X, (int)destinationIndices.Y].addElement(element);
            }
        }

        public void removeElement(LevelElement element)
        {
            Vector2 cellIndices = getCellIndices(element.Center);
            cells[(int)cellIndices.X, (int)cellIndices.Y].removeElement(element);

            if (element.isDynamic())
                dynamicElements.Remove(element);
        }

        private Vector2 getCellIndices(Vector2 position)
        {
            return new Vector2((float)Math.Floor(position.X / LevelCell.SIZE), (float)Math.Floor(position.Y / LevelCell.SIZE));
        }

        public void addToCell(LevelElement element)
        {
            Vector2 indices = getCellIndices(element.Center);
            cells[(int)indices.X, (int)indices.Y].addElement(element);
        }

        public void addElement(LevelElement element)
        {
            element.setReal();
            if (cells[cells.GetLength(0) - 1, 0].Right < element.Right || cells[0, cells.GetLength(1) - 1].Bottom < element.Bottom)
            {
                if (Right < element.Right)
                    Right = element.Right;

                if (Bottom < element.Bottom)
                    Bottom = element.Bottom;

                int numCellsX = (int)Math.Ceiling(Right / LevelCell.SIZE);
                int numCellsY = (int)Math.Ceiling(Bottom / LevelCell.SIZE);

                int oldNumCellsX = cells.GetLength(0);
                int oldNumCellsY = cells.GetLength(1);

                if (numCellsX > oldNumCellsX || numCellsY > oldNumCellsY)
                {
                    LevelCell[,] oldCells = cells;
                    cells = new LevelCell[numCellsX, numCellsY];

                    for (int x = 0; x < numCellsX; x++)
                    {
                        for (int y = 0; y < numCellsY; y++)
                        {
                            if (x < oldNumCellsX && y < oldNumCellsY)
                                cells[x, y] = oldCells[x, y];

                            else
                                cells[x, y] = new LevelCell(new Vector2(x, y));
                        }
                    }
                }
            }

            if (element.isDynamic())
                dynamicElements.Add(element);

            addToCell(element);
        }

        public void paginateElements(List<LevelElement> elements)
        {
            foreach (LevelElement element in elements)
            {
                if (extremities.X < element.Right)
                    extremities.X = element.Right;

                if (extremities.Y < element.Bottom)
                    extremities.Y = element.Bottom;
            }

            int numCellsX = (int)Math.Ceiling(Right / LevelCell.SIZE);
            int numCellsY = (int)Math.Ceiling(Bottom / LevelCell.SIZE);

            if (numCellsX < 1)
                numCellsX = 1;

            if (numCellsY < 1)
                numCellsY = 1;

            cells = new LevelCell[numCellsX, numCellsY];

            for (int x = 0; x < numCellsX; x++)
                for (int y = 0; y < numCellsY; y++)
                    cells[x, y] = new LevelCell(new Vector2(x, y));

            foreach (LevelElement element in elements)
            {
                element.setReal();

                if (element.isDynamic())
                    dynamicElements.Add(element);

                addToCell(element);
            }
        }

        public LevelElement getElementAt(Vector2 position, bool background, bool obstacle, bool foreground)
        {
            LevelElement element = null;

            Vector2 cellIndices = getCellIndices(position);

            int minX = (int)cellIndices.X - 1;
            int maxX = (int)cellIndices.X + 1;
            int minY = (int)cellIndices.Y - 1;
            int maxY = (int)cellIndices.Y + 1;

            int lengthX = cells.GetLength(0);
            int lengthY = cells.GetLength(1);

            if (minX < 0)
                minX = 0;

            if (maxX > lengthX - 1)
                maxX = lengthX - 1;

            if (minY < 0)
                minY = 0;

            if (maxY > lengthY - 1)
                maxY = lengthY - 1;

            if (minX <= maxX && minY <= maxY)
            {
                for (int x = minX; element == null && x <= maxX; x++)
                {
                    for (int y = minY; element == null && y <= maxY; y++)
                    {
                        List<LevelElement> elements = cells[x, y].getElements();
                        for (int i = 0; element == null && i < elements.Count; i++)
                        {
                            LevelElement currentElement = elements[i];
                            Rectangle rectangle = currentElement.getRectangle();
                                if (rectangle.Contains((int)position.X, (int)position.Y) &&
                                    (background && currentElement is BackgroundElement
                                  || obstacle   && (currentElement is ObstacleElement || currentElement is SceneryElement)
                                  || foreground && currentElement is ForegroundElement))
                                    element = currentElement;
                        }
                    }
                }
            }

            return element;
        }

        public bool isColliding(Vector2 position)
        {
            bool colliding = false;

            Vector2 cellIndices = getCellIndices(position);

            int minX = (int)cellIndices.X - 1;
            int maxX = (int)cellIndices.X + 1;
            int minY = (int)cellIndices.Y - 1;
            int maxY = (int)cellIndices.Y + 1;

            int lengthX = cells.GetLength(0);
            int lengthY = cells.GetLength(1);

            if (minX < 0)
                minX = 0;

            if (maxX > lengthX - 1)
                maxX = lengthX - 1;

            if (minY < 0)
                minY = 0;

            if (maxY > lengthY - 1)
                maxY = lengthY - 1;

            if (minX <= maxX && minY <= maxY)
            {
                for (int x = minX; !colliding && x <= maxX; x++)
                {
                    for (int y = minY; !colliding && y <= maxY; y++)
                    {
                        List<ObstacleElement> obstacleElements = cells[x, y].getObstacleElements();
                        for (int i = 0; !colliding && i < obstacleElements.Count; i++)
                        {
                            Rectangle rectangle = obstacleElements[i].getCollisionRectangle();
                            colliding = rectangle.Contains((int)position.X, (int)position.Y);

                            if (colliding)
                                break;
                        }
                    }
                }
            }

            return colliding;
        }

        public Carot getCollidingCarot(Rectangle rectangle)
        {
            Carot collidingCarot = null;

            Vector2 topLeftIndices = getCellIndices(new Vector2(rectangle.Left, rectangle.Top));
            Vector2 bottomRightIndices = getCellIndices(new Vector2(rectangle.Right, rectangle.Bottom));

            int minX = (int)topLeftIndices.X - 1;
            int maxX = (int)bottomRightIndices.X + 1;
            int minY = (int)topLeftIndices.Y - 1;
            int maxY = (int)bottomRightIndices.Y + 1;

            int lengthX = cells.GetLength(0);
            int lengthY = cells.GetLength(1);

            if (minX < 0)
                minX = 0;

            if (maxX > lengthX - 1)
                maxX = lengthX - 1;

            if (minY < 0)
                minY = 0;

            if (maxY > lengthY - 1)
                maxY = lengthY - 1;

            if (minX <= maxX && minY <= maxY)
            {
                for (int x = minX; x <= maxX; x++)
                {
                    for (int y = minY; collidingCarot == null && y <= maxY; y++)
                    {
                        List<Carot> carots = cells[x, y].getCarots();
                        foreach (Carot carot in carots)
                        {
                            Rectangle intersection = Rectangle.Intersect(rectangle, carot.getRectangle());
                            if (!intersection.IsEmpty)
                            {
                                collidingCarot = carot;
                                break;
                            }
                        }
                    }
                }
            }

            return collidingCarot;
        }

        public Enemy getCollidingEnemy(Rectangle rectangle)
        {
            Enemy collidingEnemy = null;

            Vector2 topLeftIndices = getCellIndices(new Vector2(rectangle.Left, rectangle.Top));
            Vector2 bottomRightIndices = getCellIndices(new Vector2(rectangle.Right, rectangle.Bottom));

            int minX = (int)topLeftIndices.X - 1;
            int maxX = (int)bottomRightIndices.X + 1;
            int minY = (int)topLeftIndices.Y - 1;
            int maxY = (int)bottomRightIndices.Y + 1;

            int lengthX = cells.GetLength(0);
            int lengthY = cells.GetLength(1);

            if (minX < 0)
                minX = 0;

            if (maxX > lengthX - 1)
                maxX = lengthX - 1;

            if (minY < 0)
                minY = 0;

            if (maxY > lengthY - 1)
                maxY = lengthY - 1;

            if (minX <= maxX && minY <= maxY)
            {
                for (int x = minX; x <= maxX; x++)
                {
                    for (int y = minY; collidingEnemy == null && y <= maxY; y++)
                    {
                        List<Enemy> enemies = cells[x, y].getEnemies();
                        foreach (Enemy enemy in enemies)
                        {
                            Rectangle intersection = Rectangle.Intersect(rectangle, enemy.getCollisionRectangle());
                            if (!intersection.IsEmpty)
                            {
                                collidingEnemy = enemy;
                                break;
                            }
                        }
                    }
                }
            }

            return collidingEnemy;
        }

        public List<Utils.Pair<ObstacleElement, Rectangle>> getCollidingObstacles(Rectangle rectangle)
        {
            List<Utils.Pair<ObstacleElement, Rectangle>> collidingObstacles = new List<Utils.Pair<ObstacleElement, Rectangle>>();

            Vector2 topLeftIndices = getCellIndices(new Vector2(rectangle.Left, rectangle.Top));
            Vector2 bottomRightIndices = getCellIndices(new Vector2(rectangle.Right, rectangle.Bottom));

            int minX = (int)topLeftIndices.X - 1;
            int maxX = (int)bottomRightIndices.X + 1;
            int minY = (int)topLeftIndices.Y - 1;
            int maxY = (int)bottomRightIndices.Y + 1;

            int lengthX = cells.GetLength(0);
            int lengthY = cells.GetLength(1);

            if (minX < 0)
                minX = 0;

            if (maxX > lengthX - 1)
                maxX = lengthX - 1;

            if (minY < 0)
                minY = 0;

            if (maxY > lengthY - 1)
                maxY = lengthY - 1;

            if (minX <= maxX && minY <= maxY)
            {
                for (int x = minX; x <= maxX; x++)
                {
                    for (int y = minY; y <= maxY; y++)
                    {
                        List<ObstacleElement> obstacleElements = cells[x, y].getObstacleElements();
                        foreach (ObstacleElement obstacleElement in obstacleElements)
                        {
                            Rectangle intersection = Rectangle.Intersect(rectangle, obstacleElement.getCollisionRectangle());
                            if (!intersection.IsEmpty)
                                collidingObstacles.Add(new Utils.Pair<ObstacleElement, Rectangle>(obstacleElement, intersection));
                        }
                    }
                }
            }

            return collidingObstacles;
        }

        public Utils.Pair<ObstacleElement, bool> getFirstCollidingObstacle(Vector2 position, float radius)
        {
            Utils.Pair<ObstacleElement, bool> collidingObstacle = new Utils.Pair<ObstacleElement,bool>();
            collidingObstacle.Second = false;

            Vector2 topLeftIndices = getCellIndices(new Vector2(position.X - radius, position.Y - radius));
            Vector2 bottomRightIndices = getCellIndices(new Vector2(position.X + radius, position.Y + radius));

            int minX = (int)topLeftIndices.X - 1;
            int maxX = (int)bottomRightIndices.X + 1;
            int minY = (int)topLeftIndices.Y - 1;
            int maxY = (int)bottomRightIndices.Y + 1;

            int lengthX = cells.GetLength(0);
            int lengthY = cells.GetLength(1);

            if (minX < 0)
                minX = 0;

            if (maxX > lengthX - 1)
                maxX = lengthX - 1;

            if (minY < 0)
                minY = 0;

            if (maxY > lengthY - 1)
                maxY = lengthY - 1;

            if (minX <= maxX && minY <= maxY)
            {
                for (int x = minX; !collidingObstacle.Second && x <= maxX; x++)
                {
                    for (int y = minY; !collidingObstacle.Second && y <= maxY; y++)
                    {
                        List<ObstacleElement> obstacleElements = cells[x, y].getObstacleElements();
                        for (int i = 0; !collidingObstacle.Second && i < obstacleElements.Count; i++)
                        {
                            ObstacleElement obstacleElement = obstacleElements[i];

                            collidingObstacle.First = obstacleElement;

                            Rectangle obstacleRectangle = obstacleElement.getCollisionRectangle();

                            Vector2 circleDistance;
                            circleDistance.X = Math.Abs(position.X - obstacleRectangle.X - obstacleRectangle.Width / 2);
                            circleDistance.Y = Math.Abs(position.Y - obstacleRectangle.Y - obstacleRectangle.Height / 2);

                            if (circleDistance.X > (obstacleRectangle.Width / 2 + radius))
                                continue;

                            if (circleDistance.Y > (obstacleRectangle.Height / 2 + radius))
                                continue;

                            if (circleDistance.X <= (obstacleRectangle.Width / 2))
                            {
                                collidingObstacle.Second = true;
                                break;
                            }
                            if (circleDistance.Y <= (obstacleRectangle.Height / 2))
                            {
                                collidingObstacle.Second = true;
                                break;
                            }

                            float cornerDistanceSquared = (float)Math.Pow((circleDistance.X - obstacleRectangle.Width / 2), 2) + (float)Math.Pow((circleDistance.Y - obstacleRectangle.Height / 2), 2);

                            if (cornerDistanceSquared <= (radius * radius))
                            {
                                collidingObstacle.Second = true;
                                break;
                            }
                        }
                    }
                }
            }

            return collidingObstacle;
        }

        public float Left
        {
            get { return 0; }
        }

        public float Right
        {
            get { return extremities.X; }
            set { extremities.X = value; }
        }

        public float Top
        {
            get { return 0; }
        }

        public float Bottom
        {
            get { return extremities.Y; }
            set { extremities.Y = value; }
        }

        public void Update(GameTime gameTime)
        {
            List<LevelElement> dynamicElements1 = new List<LevelElement>();
            foreach (LevelElement dynamicElement in dynamicElements)
                dynamicElements1.Add(dynamicElement);

            foreach (LevelElement dynamicElement in dynamicElements1)
                dynamicElement.Update(gameTime);
        }

        public void updateDynamic(LevelElement element)
        {
            if (element.isDynamic())
                dynamicElements.Add(element);

            else
                dynamicElements.Remove(element);
        }

        public void queueBackgroundDrawing(BackgroundElement element)
        {
            drawnBackgrounds.Add(element);
        }

        public void queueForegroundDrawing(ForegroundElement element)
        {
            drawnForegrounds.Add(element);
        }

        public void queueObjectDrawing(ILevelObject element)
        {
            drawnObjects.Add(element);
        }

        public void drawBackgrounds(SpriteBatch spriteBatch, Camera camera)
        {
            foreach (BackgroundElement drawnBackground in drawnBackgrounds)
                drawnBackground.Draw(spriteBatch, camera);
        }

        public void drawForegrounds(SpriteBatch spriteBatch, Camera camera)
        {
            foreach (ForegroundElement drawnForeground in drawnForegrounds)
                drawnForeground.Draw(spriteBatch, camera);
        }

        public void drawObjects(SpriteBatch spriteBatch, Camera camera)
        {
            drawnObjects.Sort(new LevelObjectSorter());
            foreach (ILevelObject drawnObject in drawnObjects)
                drawnObject.Draw(spriteBatch, camera);
        }

        public void prepareDrawing(Camera camera)
        {
            Vector2 cameraTopLeft = new Vector2(camera.Left, camera.Top);
            Vector2 cameraBottomRight = new Vector2(camera.Right, camera.Bottom);

            Vector2 topLeftCellIndices = getCellIndices(cameraTopLeft);
            Vector2 bottomRightCellIndices = getCellIndices(cameraBottomRight);

            int minX = (int)topLeftCellIndices.X - 1;
            int maxX = (int)bottomRightCellIndices.X + 1;
            int minY = (int)topLeftCellIndices.Y - 1;
            int maxY = (int)bottomRightCellIndices.Y + 1;

            int lengthX = cells.GetLength(0);
            int lengthY = cells.GetLength(1);
            
            if (minX < 0)
                minX = 0;

            if (maxX > lengthX - 1)
                maxX = lengthX - 1;

            if (minY < 0)
                minY = 0;

            if (maxY > lengthY - 1)
                maxY = lengthY - 1;

            drawnBackgrounds.Clear();
            drawnObjects.Clear();
            drawnForegrounds.Clear();

            for (int x = minX; x <= maxX; x++)
                for (int y = minY; y <= maxY; y++)
                    cells[x, y].queueDrawing(camera);
        }

        public void addPointMark(editor.PointMark pointMark)
        {
            pointMarks[pointMark.getName()] = pointMark;
        }

        public Vector2 getPointMarkPosition(string name)
        {
            if (pointMarkExists(name))
                return pointMarks[name].getPosition();

            return new Vector2();
        }

        public bool pointMarkExists(string name)
        {
            return pointMarks.ContainsKey(name);
        }

        public void addZoneMark(string name, Rectangle zone)
        {
            zoneMarks[name] = zone;
        }

        public Dictionary<string, editor.PointMark> getPointsMarks()
        {
            return pointMarks;
        }

        public Dictionary<string, Rectangle> getZoneMarks()
        {
            return zoneMarks;
        }

        public List<LevelElement> getElements()
        {
            List<LevelElement> elements = new List<LevelElement>();
            foreach (LevelCell cell in cells)
            {
                foreach (LevelElement element in cell.getElements())
                    elements.Add(element);
            }
            return elements;
        }

        public void deleteMarks(string markName)
        {
            if (pointMarks.ContainsKey(markName))
                pointMarks.Remove(markName);

            if (zoneMarks.ContainsKey(markName))
                zoneMarks.Remove(markName);
        }

        public int getNumCarots()
        {
            int n = 1;
            while (pointMarks.ContainsKey("carot " + n))
                n++;

            return n - 1;
        }
    }
}
